/***************************************************************************

Copyright (c) Microsoft Corporation. All rights reserved.
This code is licensed under the Visual Studio SDK license terms.
THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.

***************************************************************************/

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Hydra.Shell.Interfaces;
using SLControlLibrary;
using Hydra.Shell;

namespace Hydra.PackageFramework
{
	public class PackageServiceProvider : IQueryServiceProvider, IDisposable
	{
		#region Public Types
		public delegate IUnknown ServiceCreatorCallback(Type serviceType);
        public EventHandler Disposed { get; set; }
        #endregion

		#region Private Types
		private class ServiceData : IDisposable
		{
			private Type serviceType;
			private IUnknown instance;
			private ServiceCreatorCallback creator;
			private bool shouldDispose;

			public ServiceData(Type serviceType, IUnknown instance, ServiceCreatorCallback callback, bool shouldDispose)
			{
				if(null == serviceType)
				{
					throw new ArgumentNullException("serviceType");
				}

				if((null == instance) && (null == callback))
				{
					throw new ArgumentNullException("instance");
				}

				this.serviceType = serviceType;
				this.instance = instance;
				this.creator = callback;
				this.shouldDispose = shouldDispose;
			}

			public IUnknown ServiceInstance
			{
				get
				{
					if(null == instance)
					{
						instance = creator(serviceType);
					}
					return instance;
				}
			}

			public Guid Guid
			{
				get { return serviceType.GUID; }
			}

			public void Dispose()
			{
				if((shouldDispose) && (null != instance))
				{
					IDisposable disp = instance as IDisposable;
					if(null != disp)
					{
						disp.Dispose();
					}
					instance = null;
				}
				creator = null;
				GC.SuppressFinalize(this);
			}
		}
		#endregion

		#region fields

		private Dictionary<Guid, ServiceData> services = new Dictionary<Guid, ServiceData>();
		private bool isDisposed;
        /// <summary>
		/// Defines an object that will be a mutex for this object for synchronizing thread calls.
		/// </summary>
		private static volatile object Mutex = new object();
        private UIThread uiThread;

		#endregion

		#region ctors
		public PackageServiceProvider(UIThread uiThread)
		{
            this.uiThread = uiThread;
		}
		#endregion

		#region IQueryServiceProvider Members

        public bool QueryService<T>(Guid guidService, out ServiceLoadOperation<T> serviceLoadOperation) where T : IUnknown
        {
            var guidInterface = typeof(T).GetGuid();
            var serviceLoadOperation2 = (ServiceLoadOperation)null;
            var serviceLoadOperation3 = (ServiceLoadOperation)null;

            serviceLoadOperation = new ServiceLoadOperation<T>(uiThread);
            serviceLoadOperation3 = serviceLoadOperation;

            serviceLoadOperation2.ServiceLoaded += (sender, e) =>
            {
                serviceLoadOperation3.SetLoaded(serviceLoadOperation2.Service);
            };

            serviceLoadOperation2.ExceptionOccured += (sender, e) =>
            {
                serviceLoadOperation3.SetException(serviceLoadOperation2.Exception);
            };

            return this.QueryService(guidService, guidInterface, out serviceLoadOperation2);
        }

        public bool QueryService(Guid guidService, Guid guidInterFace, out ServiceLoadOperation serviceLoadOperation)
        {
            ServiceData serviceInstance = null;
            
            serviceLoadOperation = new ServiceLoadOperation(uiThread);

            if (services != null && services.ContainsKey(guidService))
            {
                serviceInstance = services[guidService];
            }

            if (serviceInstance == null)
            {
                return false;
            }

            // Now check to see if the user asked for an IID other than
            // IUnknown.  If so, we must do another QI.
            //
            if (guidInterFace.Equals(CommonInterfaces.IID_IUnknown))
            {
                serviceLoadOperation.DelaySetLoaded(serviceInstance.ServiceInstance);
                return true;
            }
            else
            {
                try
                {
                    IUnknown ppvObject;

                    if (serviceInstance.ServiceInstance.QueryInterface(guidInterFace, out ppvObject))
                    {
                        serviceLoadOperation.DelaySetLoaded(ppvObject);
                        return true;
                    }
                }
                finally
                {
                }
            }

            return false;
        }

        public bool QueryInterface(Guid iid, out IUnknown interfaceObject)
        {
            throw new NotImplementedException();
        }

        public bool QueryInterface<T>(out T interfaceObject)
        {
            throw new NotImplementedException();
        }

		#endregion

		#region Dispose

		/// <summary>
		/// The IDispose interface Dispose method for disposing the object determinastically.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);

            Disposed(this, EventArgs.Empty);

        }

		#endregion

		/// <summary>
		/// Adds the given service to the service container.
		/// </summary>
		/// <param name="serviceType">The type of the service to add.</param>
		/// <param name="serviceInstance">An instance of the service.</param>
		/// <param name="shouldDisposeServiceInstance">true if the Dipose of the service provider is allowed to dispose the sevice instance.</param>
        public void AddService(Type serviceType, IUnknown serviceInstance, bool shouldDisposeServiceInstance)
		{
			// Create the description of this service. Note that we don't do any validation
			// of the parameter here because the constructor of ServiceData will do it for us.
			ServiceData service = new ServiceData(serviceType, serviceInstance, null, shouldDisposeServiceInstance);

			// Now add the service desctription to the dictionary.
			AddService(service);
		}

        public void AddService(Type serviceType, ServiceCreatorCallback callback, bool shouldDisposeServiceInstance)
		{
			// Create the description of this service. Note that we don't do any validation
			// of the parameter here because the constructor of ServiceData will do it for us.
			ServiceData service = new ServiceData(serviceType, null, callback, shouldDisposeServiceInstance);

			// Now add the service desctription to the dictionary.
			AddService(service);
		}

		private void AddService(ServiceData data)
		{
			// Make sure that the collection of services is created.
			if(null == services)
			{
				services = new Dictionary<Guid, ServiceData>();
			}

			// Disallow the addition of duplicate services.
			if(services.ContainsKey(data.Guid))
			{
				throw new InvalidOperationException();
			}

			services.Add(data.Guid, data);
		}

		/// <devdoc>
		/// Removes the given service type from the service container.
		/// </devdoc>
		public void RemoveService(Type serviceType)
		{
			if(serviceType == null)
			{
				throw new ArgumentNullException("serviceType");
			}

			if(services.ContainsKey(serviceType.GUID))
			{
				services.Remove(serviceType.GUID);
			}
		}

		#region helper methods
		/// <summary>
		/// The method that does the cleanup.
		/// </summary>
		/// <param name="disposing"></param>
		protected virtual void Dispose(bool disposing)
		{
			// Everybody can go here.
			if(!this.isDisposed)
			{
				// Synchronize calls to the Dispose simulteniously.
				lock(Mutex)
				{
					if(disposing)
					{
						// Remove all our services
						if(services != null)
						{
							foreach(ServiceData data in services.Values)
							{
								data.Dispose();
							}
							services.Clear();
							services = null;
						}
					}

					this.isDisposed = true;
				}
			}
		}
		#endregion

        public bool IsDisposed
        {
            get 
            {
                return isDisposed;
            }
        }
    }
}
